#ifndef _NETWORK_H_
#define _NETWORK_H_

#include "eccommon_pch.h"

NAMESPACE_START {
	namespace SerializationServer {
		class Connection { 
		public:
			Connection(boost::asio::io_service &ioService);
			boost::asio::ip::tcp::socket &getSocket();

			template<typename T,typename Handler>
			void AsyncWrite(const T &obj, Handler handler)
			{
				//serialize the object
				std::ostringstream archiveStream;
				boost::archive::text_oarchive archive(archiveStream);
				archive << obj;
				_outboundData = archiveStream.str();

				//build the header
				std::ostringstream headerStream;
				headerStream << std::setw(HEADER_LENGTH) << std::hex << _outboundData.size();
				if(!headerStream || headerStream.str().size() != HEADER_LENGTH)
				{
					boost::system::error_code error(boost::asio::error::invalid_argument);
					_socket.get_io_service().post(boost::bind(handler, error));
					return;
				}
				_outboundHeader = headerStream.str();

				//write the serialized data to the socket
				std::vector<boost::asio::const_buffer> buffers;
				buffers.push_back(boost::asio::buffer(_outboundHeader));
				buffers.push_back(boost::asio::buffer(_outboundData));
				boost::asio::async_write(_socket, buffers, handler);
			}

			template<typename T,typename Handler>
			void AsyncRead(T &t, Handler handler)
			{
				auto f = &Connection::HandleReadHeader<T, Handler>;

				boost::asio::async_read(_socket, boost::asio::buffer(_inboundHeader),
					boost::bind(f, this, boost::asio::placeholders::error, boost::ref(t),
					std::make_tuple(handler)));
			}

		private:
			template<typename T, typename Handler>
			void HandleReadHeader(const boost::system::error_code &e, T &t, std::tuple<Handler> handler)
			{
				if(e)
				{
					std::get<0>(handler)(e);
				}
				else
				{
					std::istringstream is(std::string(_inboundHeader, HEADER_LENGTH));
					std::size_t inboundDataSize = 0;
					if(!(is >> std::hex >> inboundDataSize))
					{
						auto error = boost::asio::error::invalid_argument;
						std::get<0>(handler)(error);
						return;
					}

					_inboundData.resize(inboundDataSize);

					auto f = &Connection::HandleReadData<T, Handler>;

					boost::asio::async_read(_socket, boost::asio::buffer(_inboundData),
						boost::bind(f, this, boost::asio::placeholders::error, boost::ref(t), handler));
				}
			}

			template<typename T, typename Handler>
			void HandleReadData(const boost::system::error_code &e, T &t, std::tuple<Handler> handler)
			{
				if(e)
				{
					std::get<0>(handler)(e);
				}
				else
				{
					//extract the data structure from the data just received
					try
					{
						std::string archiveData(&_inboundData[0], _inboundData.size());
						std::istringstream archiveStream(archiveData);
						boost::archive::text_iarchive archive(archiveStream);
						archive >> t;
					}
					catch(std::exception)
					{
						//unable to decode data
						auto error(boost::asio::error::invalid_argument);
						std::get<0>(handler)(error);
						return;
					}

					//inform the caller that the data has been received okay.
					std::get<0>(handler)(e);
				}
			}

		private:
			static const int HEADER_LENGTH = 8;
			boost::asio::ip::tcp::socket _socket;
			std::string _outboundHeader;
			std::string _outboundData;
			char _inboundHeader[HEADER_LENGTH];
			std::vector<char> _inboundData;
		};
		typedef std::shared_ptr<Connection> ConnectionPtr;

		template<typename DataT>
		class Server {
		public:
			typedef std::shared_ptr<Server<DataT>> Ptr;
			typedef boost::function<std::vector<DataT>()> OnGetDataFunction;

			Server(boost::asio::io_service &ioService, unsigned short port, OnGetDataFunction &onGetData)
				: _acceptor(ioService, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 
				port)),_onGetData(onGetData)
			{
				StartAccept();
			}
			virtual void HandleAccept(const boost::system::error_code &e, ConnectionPtr connection)
			{
				if(!e)
				{
					std::vector<DataT> data = _onGetData();

					connection->AsyncWrite(data, 
						boost::bind(&Server::HandleWrite, this,
						boost::asio::placeholders::error, connection));
				}
				else
				{
					//TODO: add logging
					std::cerr << e.message() << std::endl;
				}

				StartAccept();
			}
			virtual void HandleWrite(const boost::system::error_code &e, ConnectionPtr connection)
			{
				//do nothing
			}
		private:
			void StartAccept()
			{
				ConnectionPtr connection(new Connection(_acceptor.get_io_service()));

				_acceptor.async_accept(connection->getSocket(),
					boost::bind(&Server::HandleAccept, this, boost::asio::placeholders::error, 
					connection));
			}
			//used to accept incoming socket connections
			boost::asio::ip::tcp::acceptor _acceptor;
			boost::function<std::vector<DataT>()> _onGetData;
		};

		template<typename DataT>
		class Client {
		public:
			typedef std::shared_ptr<Client<DataT>> Ptr;
			typedef boost::function<void(std::vector<DataT>&)> OnDataReceivedFunction;

			Client(boost::asio::io_service &ioService,
				const std::string &host, const std::string &service, OnDataReceivedFunction &onDataReceived)
				: _connection(ioService), _onDataReceived(onDataReceived)
			{
				//resolve the hostname
				boost::asio::ip::tcp::resolver resolver(ioService);
				boost::asio::ip::tcp::resolver::query query(host, service);
				boost::asio::ip::tcp::resolver::iterator endpointIterator = resolver.resolve(query);

				//start the connection operation
				boost::asio::async_connect(_connection.getSocket(), endpointIterator,
					boost::bind(&Client::HandleConnect, this, boost::asio::placeholders::error));
			}
		private:
			void HandleConnect(const boost::system::error_code &e)
			{
				if(!e)
				{
					_connection.AsyncRead(_buffer,
						boost::bind(&Client::HandleRead, this,
						boost::asio::placeholders::error));
				}
				else
				{
					//TODO: add logging
					std::cerr << e.message() << std::endl;
				}
			}
			void HandleRead(const boost::system::error_code &e)
			{
				if(!e)
				{
					_onDataReceived(_buffer);
				}
				else
				{
					//TODO: add logging
					std::cerr << e.message() << std::endl;
				}
			}
		private:
			Connection _connection;
			OnDataReceivedFunction _onDataReceived;
			std::vector<DataT> _buffer;
		};
	}}

#endif